#include <strmif.h>
#include "mkvcontext.hpp"
#include "mkvstreamaudioac3.hpp"
#include "cmediatypes.hpp"
#include <cassert>
#include <uuids.h>
#ifdef _DEBUG
#include <odbgstream.hpp>
#include <iomanip>
using std::endl;
using std::hex;
using std::dec;
using std::setfill;
using std::setw;
#endif

//Audio Substream Headers
//http://www.mpucoder.com/DVD/ass-hdr.html
//
//AC3 frames are 32ms in length, or 2880 ticks of the 90KHz clock. The resulting 
//frame rate is 31.25 fps.
//
//AC3 (Dolby Digital) Headers
//http://www.mpucoder.com/DVD/ac3hdr.html
//
//Digital Audio Compression (AC-3, E-AC3) Standard, Rev. B
//http://www.atsc.org/standards/a_52b.pdf
//
//AC-3 Compressed Audio (Dolby Digital), Revision A:
//http://www.digitalpreservation.gov/formats/fdd/fdd000209.shtml
//
//http://web.archive.org/web/20060207093602/http://www.sparta.lu.se/~bjorn/whitney/references/ac3spec.pdf
//Chapter 5 (p. 12) describes Sync Frame.

namespace MkvMux
{


void MkvStreamAudioAc3::GetMediaTypes(CMediaTypes& mtv)
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Audio;
    mt.subtype = MEDIASUBTYPE_DOLBY_AC3;
    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    mtv.Add(mt);
}


bool MkvStreamAudioAc3::QueryAccept(const AM_MEDIA_TYPE& mt)
{
    if (mt.majortype != MEDIATYPE_Audio)
        return false;
        
    if (mt.subtype != MEDIASUBTYPE_DOLBY_AC3)
        return false;
        
    if (mt.formattype != FORMAT_WaveFormatEx)
        return false;
    
    if (mt.cbFormat < sizeof(WAVEFORMATEX))
        return false;
        
    if (mt.pbFormat == 0)
        return false;

    const WAVEFORMATEX& wfx = (WAVEFORMATEX&)(*mt.pbFormat);
    wfx;    

    //TODO: anything else here?
    
    return true;
}


HRESULT MkvStreamAudioAc3::GetAllocatorRequirements(
    const AM_MEDIA_TYPE&,
    ALLOCATOR_PROPERTIES& props)
{
    props.cBuffers = 2;  //TODO: why 2 here?
    props.cbBuffer = 0;
    props.cbAlign = 0;
    props.cbPrefix = 0;

    return S_OK;
}


MkvStreamAudio* MkvStreamAudioAc3::CreateStream(
    MkvContext& ctx,
    const AM_MEDIA_TYPE& mt)
{
   assert(mt.majortype == MEDIATYPE_Audio);
   assert(mt.formattype == FORMAT_WaveFormatEx);
   
   const ULONG cb = mt.cbFormat;
   assert(cb >= sizeof(WAVEFORMATEX));

   const BYTE* const pb = mt.pbFormat;
   assert(pb);
   
   return new (std::nothrow) MkvStreamAudioAc3(ctx, pb, cb);
}


MkvStreamAudioAc3::Ac3Frame::Ac3Frame(Ac3Parser::Frame* f) :
    m_pFrame(f)
{
}


MkvStreamAudioAc3::Ac3Frame::~Ac3Frame()
{
   Ac3Parser::Frame::Destroy(m_pFrame);
}


ULONG MkvStreamAudioAc3::Ac3Frame::curr_timecode_ms() const
{
   return m_pFrame->timecode_ms();
}


ULONG MkvStreamAudioAc3::Ac3Frame::size() const
{
   return m_pFrame->buflen();
}


const BYTE* MkvStreamAudioAc3::Ac3Frame::data() const
{
   return m_pFrame->buf();
}


MkvStreamAudioAc3::MkvStreamAudioAc3(
    MkvContext& ctx,
    const BYTE* pb,
    ULONG cb) :
    MkvStreamAudio(ctx, pb, cb)
{
}


const WAVEFORMATEX& MkvStreamAudioAc3::GetFormat() const
{
    ULONG cb;
    const void* const pv = MkvStreamAudio::GetFormat(cb);
    assert(pv);
    assert(cb >= 18);
    
    const WAVEFORMATEX* const pwfx = static_cast<const WAVEFORMATEX*>(pv);
    assert(pwfx);
    assert(cb >= (18 + ULONG(pwfx->cbSize)));
    
    const WAVEFORMATEX& wfx = *pwfx;
    return wfx;
}


ULONG MkvStreamAudioAc3::GetSamplesPerSec() const
{
    const WAVEFORMATEX& wfx = GetFormat();
    assert(wfx.nSamplesPerSec > 0);

    return wfx.nSamplesPerSec;
}


BYTE MkvStreamAudioAc3::GetChannels() const
{
    const WAVEFORMATEX& wfx = GetFormat();
    assert(wfx.nChannels > 0);
    assert(wfx.nChannels <= 255);

    return static_cast<BYTE>(wfx.nChannels);
}


//void MkvStreamAudioAc3::WriteTrackName()
//{
//    EbmlIO::File& f = m_context.m_file;
//
//    f.WriteID2(0x536E);   //name
//    f.Write1UTF8(L"AC-3 audio stream");
//}


void MkvStreamAudioAc3::WriteTrackCodecID()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID1(0x86);  //Codec ID
    f.Write1String("A_AC3");    //TODO

    //TODO:    
    //http://www.matroska.org/technical/specs/codecid/index.html
    //
    //BSID <= 8 !! The private data is void ??? 
    //  Corresponding ACM wFormatTag : 0x2000 ; channel number have to be read 
    //  from the corresponding audio element.
    //
    //A_AC3/BSID9
    //A_AC3/BSID10 
    //
    //AC3/BSID9 and AC3/BSID10 (DolbyNet) : 
    //The ac3 frame header has, similar to the mpeg-audio header a version field. 
    //Normal ac3 is defined as bitstream id 8 (5 Bits, numbers are 0-15). 
    //Everything below 8 is still compatible with all decoders that handle 8 
    //correctly. Everything higher are additions that break decoder compatibility.
    //For the samplerates 24kHz (00); 22,05kHz (01) and 16kHz (10) the BSID is 9.
    //For the samplerates 12kHz (00); 11,025kHz (01) and 8kHz (10) the BSID is 10.
    //END TODO    
}


void MkvStreamAudioAc3::WriteTrackCodecName()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID3(0x258688);  //Codec Name
    f.Write1UTF8(L"AC-3");
}



HRESULT MkvStreamAudioAc3::Receive(IMediaSample* pSample)
{
    assert(pSample);

    if (m_context.m_file.GetStream() == 0)
        return S_OK;  //TODO: send EOS immediately (and return S_FALSE)

    BYTE* ptr;

    HRESULT hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    hr;
         
    const long len = pSample->GetActualDataLength();
    assert(len >= 0);

    m_parser.Enqueue(ptr, len);

    while (Ac3Parser::Frame* f = m_parser.Dequeue())
    {
      //We might have to create a generic "audio frame" class
      //or just "frame" class containing audio-specific things,
      //generic so that it can be shared among all possible
      //audio input streams.  Here the Ac3Parser::Frame type
      //could just derive from this abstract base class.
      //We could use refcounting the same as we do for 
      //dshow types.

      //Alternatively, at the point where we make the
      //decision to write the frame (decide which cluster),
      //we could just guery the stream an opaque way, 
      //such as "what is your earliest time", and "write
      //a frame now".  So the context would talke to the
      //stream, instead of dealing with frames directly.
      //This has the benefit of isolating parser details
      //(e.g. Ac3Parser::Frame) for a stream from muxer details.

      Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
      assert(pFrame);  //TODO

      m_context.NotifyAudioFrame(this, pFrame);
    }
        
    return S_OK;
}


int MkvStreamAudioAc3::EndOfStream()
{
    if (m_context.m_file.GetStream())
    {
        m_parser.EndOfStream();

        while (Ac3Parser::Frame* f = m_parser.Dequeue())
        {
            Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
            assert(pFrame);  //TODO

            m_context.NotifyAudioFrame(this, pFrame);
        }
    }
       
    return m_context.NotifyAudioEOS(this);
}


void MkvStreamAudioAc3::Flush()
{
    if (m_context.m_file.GetStream())
    {
        m_parser.EndOfStream();

        while (Ac3Parser::Frame* f = m_parser.Dequeue())
        {
            Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
            assert(pFrame);  //TODO

            m_context.NotifyAudioFrame(this, pFrame);
        }
    }

    MkvStreamAudio::Flush();
}



}  //end namespace MkvMux
